Car and trailer path visualizer

by Anton Lukyanenko and Alejandro Bravo-Doddoli, December 2024. For math questions, email Alejandro. For Mathematica questions, email Anton.

Based on equations in
https://public.websites.umich.edu/~abravodo/Naranjo.pdf

J is moment of inertia of the trailer (<m (l^2))
(Subscript[J, 0]) is the moment of inertia of the car (which doesn’t matter)
M and m are the masses of the car and trailer
l is the distance between the car and trailer

u is the linear velocity of the car
w is the angular velocity of the car
α is the angle between the car and the trailer

J=.5 ;
J0 = 1;
M=1;
m=1;
l=1;
minTime=-20;
maxTime = 20;

We can enter starting conditions manually:

StartingLinearVelocity=1.;
StartingAngularVelocity=π/3.;
StartingRelAngle=π/4.;

Alternately, we get interesting paths if we calculate the critical energy:

StartingAngularVelocity=π/3.;
StartingRelAngle=π/4.;
ECritical=(1/2)(J0+J+M (l^2))(StartingAngularVelocity^2);
R=M + m (((Cos[StartingRelAngle]))^2)+(J/(l^2))(((Sin[StartingRelAngle]))^2);
StartingLinearVelocity = (Sqrt[((2 ECritical - J0 (StartingAngularVelocity^2) )/R)]);

solution=NDSolve[{
u'[t]==(m (l^2)-J)u [t] Cos[α[t]]Sin[α[t]](l w[t] - u [t]Sin[α[t]])/(l((M+m (((Cos[α[t]]))^2))(l^2)+J ((Sin[α[t]])^2))),
w'[t]==0,
α'[t]==w[t]-((u[t] Sin[α[t]])/l),
CarX'[t]==u[t] Cos[θ[t]],
CarY'[t]==u[t] Sin[θ[t]],
θ'[t]==w[t],
u[0]==StartingLinearVelocity,
w[0]==StartingAngularVelocity,
α[0]==StartingRelAngle,
CarX[0]==0,
CarY[0]==0,
θ[0]==0
},
{CarX[t],CarY[t],θ[t],α[t]},
{t,minTime,maxTime}];

CarX[t_]=CarX[t]/.solution[[1]];
CarY[t_]=CarY[t]/.solution[[1]];
θ[t_]=θ[t]/.solution[[1]];
α[t_]=α[t]/.solution[[1]];
CarAngle[t_]:=θ[t];
CarLocation[t_]:={CarX[t],CarY[t]};
CarVector[t_]:={Cos[θ[t]],Sin[θ[t]]};
TrailerAbsoluteAngle[t_]:=θ[t]-α[t];
TrailerVector[t_]:= {Cos[TrailerAbsoluteAngle[t]],Sin[TrailerAbsoluteAngle[t]]};
TrailerLocation[t_]:=CarLocation[t]-l TrailerVector[t];

Car=RemoveBackground[,{"Background",White}]
Trailer=RemoveBackground[,{"Background",White}]

Manipulate[
Show[
ParametricPlot[{CarLocation[t],TrailerLocation[t]},{t,minTime,maxTime}],
Graphics[Arrow[{CarLocation[s],CarLocation[s]+.5{Cos[θ[s]],Sin[θ[s]]}}]],
Graphics[Arrow[{TrailerLocation[s],CarLocation[s]}]],
Graphics[
Inset[Car,CarLocation[s],Automatic,.2,CarVector[s]]
],
Graphics[
Inset[Trailer,TrailerLocation[s],Automatic,.2,TrailerVector[s]]
]
],
{{s,0},minTime,maxTime}]

numFrames=120;
VideoFrames=Table[
Show[
ParametricPlot[{CarLocation[t],TrailerLocation[t]},{t,minTime,maxTime}],
Graphics[Arrow[{CarLocation[s],CarLocation[s]+.5{Cos[θ[s]],Sin[θ[s]]}}]],
Graphics[Arrow[{TrailerLocation[s],CarLocation[s]}]],
Graphics[
Inset[Car,CarLocation[s],Automatic,.2,CarVector[s]]
],
Graphics[
Inset[Trailer,TrailerLocation[s],Automatic,.2,TrailerVector[s]]
]
],
{s,minTime,maxTime,((maxTime-minTime)/numFrames)}];

SetDirectory[NotebookDirectory[]]

For export, DisplayDurations specifies the pause between frames in the gif. Looks like the default is 0.1, so 10 frames per second.

Export["CarAndTrailerTrajectory1.gif",VideoFrames,"DisplayDurations"->.1]